section .data
	file_read dd 0
	mode_read db 'rb',0
	file_write dd 0
	mode_write db "wb",0
	; Размер буфера операций чтения/записи (в байтах)
	io_buffer_size equ 4096
	; Указатели на буферы операций чтения/записи
	i_buffer times io_buffer_size dd 0
	o_buffer times io_buffer_size dd 0
	; Массив кодов
	code times 100h dd 0
	; Массив размерностей кодов
	num_bit times 100h db 0
	; Указатель на вершину списка стоков дерева 
	head_list dd 0
	; Указатель на следующий за концом массива элементов списка байт
	end_array dd 0
	; Указатель на начало выделенной под дерево области памяти
	begin_allocated_memory dd 0
	; Указатель на корень дерева
	root dd 0
	; Количество узлов в дереве
	tree_size dw 0
	; Константа SEEK_SET для вызова функции fread
	SEEK_SET dd 0
	
	; Узел дерева/элемент списка
	; Для наглядности я намерено не пользуюсь макросами STRUC и ENDSTRUC (поддержка структур в nasm)
	; Структура узла дерева: 
	;						1 байт - код символа
	;						4 + 4 = 8 байт - указатели на левого и правого сына дерева (0 - у стоков)
	;						4 + 4 = 8 байт - указатели на предыдущий/следующий элемент в связном списке
	;						4 байта - количество вхождений символа
	;						Итого: 21 байт
	; Размер узла дерева
	elem_size equ 17
	; Смещение кода символа
	; [начало_элемента] - код символа (1 байт)
	elem_code_dis equ 0
	; Смещение адреса левого поддерева (дерево)
	; [начало_элемента + 1] - указатель на правого сына (4 байта)
	elem_left_dis equ 1
	; Смещение адреса правого поддерева (дерево)
	; [начало_элемента + 5] - указатель на левого сына (4 байта)
	elem_right_dis equ 5
	; Смещение адреса следующего элемента (список)
	; [начало_элемента + 9] - указатель на следующий элемент в списке (4 байта)
	elem_next_dis equ 9
	; Смещение количества вхождений
	; [начало_элемента + 13] - количество вхождений символа в файл (4 байта)
	elem_num_dis equ 13

	; Коды ошибок и их описания
	; Успех
	DESC_no_error db 'Success',0
	; Невозможно закрыть сжатый файл после записи
	C_close_write_error equ 01h
	DESC_C_close_write_error db 'Can not close compress file after write',0
	; Невозможно открыть сжатый файл для записи
	C_open_write_error equ 02h
	DESC_C_open_write_error db 'Can not open compress file for write',0
	; Невозможно открыть сжимаемый файл для чтения, однако в прошлом было возможно
	CF_open_read_error equ 03h
	DESC_CF_open_read_error db 'Can not open compressed file for read, but in last we do it',0
	; Невозможно закрыть сжимаемый файл после чтения, однако в прошлом было возможно
	CF_close_read_error equ 04h
	DESC_CF_close_read_error db 'Can not close compressed file after read, but in last we do it',0
	; Переполнение. Файл слишком большой для сжатия
	CCA_overflow_error equ 05h
	DESC_CCA_overflow_error db 'Overflow. File too big for compress (>4Gb)',0
	; Невозможно открыть сжимаемый файл для чтения
	RIF_open_read_error equ 06h
	DESC_RIF_open_read_error db 'Can not open compressed file for read',0
	; Невозможно закрыть сжимаемый файл после чтения
	RIF_close_read_error equ 07h
	DESC_RIF_close_read_error db 'Can not close compressed file after read',0
	; Ошибка при выделении памяти
	CL_malloc_error equ 08h
	DESC_CL_malloc_error db 'Memory allocated error',0
	; Невозможно открыть сжатый файл для чтения
	D_open_read_error equ 09h
	DESC_D_open_read_error db 'Can not open compress file for read',0
	; Невозможно закрыть сжатый файл после чтения
	D_close_read_error equ 0Ah
	DESC_D_close_read_error db 'Can not close compress file after read',0
	; Исходный сжатый файл слишком мал. Повреждена таблица восстановления дерева Хаффмана. Сжатое содержимое отсутствует
	RTSENFF_read_file_too_small equ 0Bh
	DESC_RTSENFF_read_file_too_small db 'Too small compress file. Haffman tree rebuild table corrupted. Compressed data not found',0
	; Невозможно открыть декомпрессированный файл для записи
	DF_open_write_error equ 0Ch
	DESC_DF_open_write_error db 'Can not open decompressed file for write',0
	; Невозможно закрыть декомпрессированный файл после записи
	DF_close_write_error equ 0Dh
	DESC_DF_close_write_error db 'Can not close decompressed file after write',0
	; Исходный сжатый файл слишком мал. Таблица восстановления дерева Хаффмана корректна, однако в файле недостаточно сжатого содержимого, чтобы восстановить исходный файл
	DF_read_file_too_small equ 0Eh
	DESC_DF_read_file_too_small db 'Too small compress file. Too many compressed data found',0
	; Входной файл - пуст
	CL_null_list equ 0Fh
	DESC_CL_null_list db 'Data not found in file',0
	; Невозможно прочитать сжимаемый файл
	RIF_read_error equ 10h
	DESC_RIF_read_error db 'Can not read compressed file',0
	; Невозможно записать таблицу количеств вхождений символов
	PT_write_error equ 11h
	DESC_PT_write_error db 'Can not write in compressed file',0
	; Невозможно прочитать исходный файл при компрессии
	CF_read_error equ 12h
	DESC_CF_read_error db 'Can not read compressed file',0
	; Невозможно записать в результирующий файл при сжатии
	CF_write_error_1 equ 13h
	DESC_CF_write_error_1 db 'Can not write in compressed file',0
	CF_write_error_2 equ 14h
	DESC_CF_write_error_2 db 'Can not write in compressed file',0
	; Невозможно прочитать сжатый файл
	DF_read_error equ 15h
	DESC_DF_read_error db 'Can not read compressed file',0
	; Невозможно записать в декомпрессированный файл при декомпресии
	DF_write_error_1 equ 16h
	DESC_DF_write_error_1 db 'Can not write in decompressed file',0
	DF_write_error_2 equ 17h
	DESC_DF_write_error_2 db 'Can not write in decompressed file',0
	; Некорректный размер декомпрессированного файла
	DF_decompress_file_size_error equ 18h
	DESC_DF_decompress_file_size_error db 'Bad information about decompress file size',0
	; Неизвестная ошибка
	DESC_unknown_error db 'Unknown error',0
	; Массив адресов описаний ошибок
	DESC_address_array: dd DESC_no_error,DESC_C_close_write_error,DESC_C_open_write_error,DESC_CF_open_read_error,DESC_CF_close_read_error,DESC_CCA_overflow_error,DESC_RIF_open_read_error,DESC_RIF_close_read_error,DESC_CL_malloc_error,DESC_D_open_read_error,DESC_D_close_read_error,DESC_RTSENFF_read_file_too_small,DESC_DF_open_write_error,DESC_DF_close_write_error,DESC_DF_read_file_too_small,DESC_CL_null_list,DESC_RIF_read_error,DESC_PT_write_error,DESC_CF_read_error,DESC_CF_write_error_1,DESC_CF_write_error_2,DESC_DF_read_error,DESC_DF_write_error_1,DESC_DF_write_error_2,DESC_DF_decompress_file_size_error,DESC_unknown_error
	DESC_address_array_end:

section .text

; Здесь содержаться константы транслятора, ответственные за определение ОС
%include "config.asm"

; Системозависящами частями программы управляем через константы транслятора 
	%ifdef LINUX
		; GNU Lib C
		extern fopen
		extern fclose
		extern ferror
		extern fseek
		extern fread
		extern fwrite
		extern feof
		extern malloc
		extern free
		; Экспортируемая функция
		; (получение константы SEEK_SET из stdio.h)
		extern get_seek_set
	%elifdef WINDOWS
		; MinGW Lib C
		extern _fopen
		extern _fclose
		extern _ferror
		extern _fseek
		extern _fread
		extern _fwrite
		extern _feof
		extern _malloc
		extern _free
		%define fopen _fopen
		%define fclose _fclose
		%define ferror _ferror
		%define fseek _fseek
		%define fread _fread
		%define fwrite _fwrite
		%define feof _feof
		%define malloc _malloc
		%define free _free
		; Импортируемые функции
		%define haffman_compress _haffman_compress
		%define haffman_decompress _haffman_decompress
		%define get_error_description _get_error_description
		; Экспортируемая функция
		; (получение константы SEEK_SET из stdio.h)
		extern _get_seek_set
		%define get_seek_set _get_seek_set
	%endif

global haffman_compress
global haffman_decompress
global get_error_description
global DESC_no_error
global DESC_C_close_write_error
global DESC_C_open_write_error
global DESC_CF_open_read_error
global DESC_CF_close_read_error
global DESC_CCA_overflow_error
global DESC_RIF_open_read_error
global DESC_RIF_close_read_error
global DESC_CL_malloc_error
global DESC_D_open_read_error
global DESC_D_close_read_error
global DESC_RTSENFF_read_file_too_small
global DESC_DF_open_write_error
global DESC_DF_close_write_error
global DESC_DF_read_file_too_small
global DESC_CL_null_list
global DESC_RIF_read_error
global DESC_RIF_read_close_error
global DESC_PT_write_error
global DESC_unknown_error

%include "all_func.asm"
%include "compress_func.asm"
%include "decompress_func.asm"

;############################################################################ 

haffman_compress:

	mov eax,dword [esp+4]
	mov [file_read],eax
	mov eax,dword [esp+8]
	mov [file_write],eax

	; Сохраняем значения регистров ebx, ecx, edx, esi, edi, ebp
	push ebx
	push ecx
	push edx
	push esi
	push edi
	push ebp
	mov ebp,esp

	; Инициализация глобальных переменных
	call init_global_variable

	; Прочитать исходный (сжимаемый) файл
	call read_input_file

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end_no_close_write

	; Создать список стоков дерева Хаффмана
	call create_list

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end_no_close_write

	; Создать дерево на основе списка стоков
	call create_tree

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end_no_close_write

	; Открываем результирующий файл на запись
	; (необходимо это сделать именно сейчас, т.к. записывать в него будут две процедуры, вызовы которых не
	; идут подряд)
	push mode_write
	push dword [file_write]
	call fopen
	test eax,eax
	jnz C_open_write_no_error
		mov al,C_open_write_error
		jmp short C_end_no_close_write

	C_open_write_no_error:

	; Файл успешно открыт => сохраним его дескриптор в стеке
	push eax

	; Запишем коды символов и количество вхождений каждого символа, встретившегося хотя бы раз
	; в исходном файле, в результирующий файл
	call put_table_symbol_enters_in_file

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end

	; Создать массивы кодов Хаффмана и размерностей кодов Хаффмана
	call create_code_array

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end

	; Сжать исходный файл на основе кодов Хаффмана
	call compress_file

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz C_end

	; Успех - возвращаем ноль
	xor al,al

	C_end:

	; Выбираем дескриптор результирующего файла
	pop edi
	; Сохраняем предыдущее значение eax (возможно какая-либо процедура вернула код ошибки => выполняющаяся
	; далее fclose может этот код переписать в ноль, чего делать нельзя)
	push ax
	; Заталкиваем дескриптор результирующего файла обратно
	push edi

	; Закрываем результирующий файл (если все сделано правильно - его дескриптор по-прежнему лежит наверху стека)
	call fclose
	test eax,eax
	jz C_close_write_no_error
		mov al,C_close_write_error
		jmp short C_end_no_close_write
	
	C_close_write_no_error:

	pop eax
	pop ax

	C_end_no_close_write:

	; Возвращаем память, отведенную под дерево, если дерево было создано
	mov edx,[begin_allocated_memory]
	test edx,edx
	jz C_no_free
		push eax
		push edx
		call free
		pop eax
		pop eax
	
	C_no_free:
		
	; Восстанавливаем регистры ebp, edi, esi, edx, ecx, ebx и стек
	mov esp,ebp
	pop ebp
	pop edi
	pop esi
	pop edx
	pop ecx
	pop ebx
	
	ret

;############################################################################ 

haffman_decompress:

	mov eax,dword [esp+4]
	mov [file_read],eax
	mov eax,dword [esp+8]
	mov [file_write],eax

	; Сохраняем значения регистров ebx, ecx, edx, esi, edi, ebp
	push ebx
	push ecx
	push edx
	push esi
	push edi
	push ebp
	mov ebp,esp

	; Инициализация глобальных переменных
	call init_global_variable

	; Открываем исходный (подлежащий декомпрессии) файл на чтение
	push mode_read
	push dword [file_read]
	call fopen
	test eax,eax
	jnz D_open_read_not_error
		mov al,D_open_read_error
		jmp short D_end

	D_open_read_not_error:

	push eax
	
	; Считывание данных о символах и количестве вхождений символов в результирующий (декомпрессированный) файл
	call read_table_symbol_enters_from_file

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz D_end_read_close

	; Создать список стоков дерева Хаффмана
	call create_list

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz D_end_read_close

	; Создать дерево на основе списка стоков
	call create_tree

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz D_end_read_close

	; Непосредственно декомпрессия файла
	call decompress_file

		; В случае ошибки будет возвращен не ноль
		test al,al
		jnz D_end_read_close

	xor al,al

	D_end_read_close:

	; Возвращаем память, отведенную под дерево, если дерево было создано
	mov edx,[begin_allocated_memory]
	test edx,edx
	jz D_no_free
		push eax
		push edx
		call free
		pop eax
		pop eax
	
	D_no_free:

	pop edi
	push ax
	push edi

	; Закрываем исходный файл
	call fclose
	test eax,eax
	jz D_close_not_error
		mov al,D_close_read_error
		jmp short D_end

	D_close_not_error:
	
	pop edi
	pop ax

	D_end:

	; Восстанавливаем регистры ebp, edi, esi, edx, ecx, ebx и стек
	mov esp,ebp
	pop ebp
	pop edi
	pop esi
	pop edx
	pop ecx
	pop ebx

	ret

;############################################################################ 

get_error_description:

	; Выбираем код ошибки. Хотя он и unsinged char, но в стеке лежит все равно как двойное слово
	mov ebx,[esp + 4]

	; Поиск переданной ошибки

	; По коду ошибки получаем смещение, по которому хранится описание ошибки
	shl ebx,2
	add ebx,DESC_address_array
	cmp ebx,DESC_address_array_end
	jb GED_end
		; Все коды, которые не соответствуют никакой ошибке и не равны нулю описываются как "неизвестная ошибка"
		mov eax,DESC_unknown_error
		ret
	GED_end:
	; Получаем смещение адреса ошибки
	mov eax,[ebx]
	ret

;############################################################################ 

